/**
    bespoke synth, a software modular synthesizer
    Copyright (C) 2021 Ryan Challinor (contact: awwbees@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/
//
//  VSTPlugin.cpp
//  Bespoke
//
//  Created by Ryan Challinor on 1/18/16.
//
//

#include "VSTPlugin.h"
#include "OpenFrameworksPort.h"
#include "SynthGlobals.h"
#include "IAudioReceiver.h"
#include "ofxJSONElement.h"
#include "ModularSynth.h"
#include "Profiler.h"
#include "Scale.h"
#include "ModulationChain.h"
#include "PatchCableSource.h"
#include "UserPrefs.h"
//#include "NSWindowOverlay.h"

namespace
{
   const int kGlobalModulationIdx = 16;
   const juce::String kInvalidPluginId = "--0-0"; //this is what's generated by juce's createIdentifierString() for an invalid PluginDescription
   juce::String GetFileNameWithoutExtension(const juce::String& fullPath)
   {
      auto lastSlash = fullPath.lastIndexOfChar('/') + 1;
      if (lastSlash == 0)
         lastSlash = fullPath.lastIndexOfChar('\\') + 1;
      auto lastDot = fullPath.lastIndexOfChar('.');

      if (lastDot > lastSlash)
         return fullPath.substring(lastSlash, lastDot);

      return fullPath.substring(lastSlash);
   }
}

using namespace juce;

namespace VSTLookup
{
   struct PluginFormatSorter
   {
      explicit PluginFormatSorter(juce::String formatOrder)
      {
         mFormatOrder.addTokens(formatOrder, ";", "");
      }

      int compareElements(const PluginDescription& first, const PluginDescription& second) const
      {
         int indexFirst = mFormatOrder.indexOf(first.pluginFormatName);
         int indexSecond = mFormatOrder.indexOf(second.pluginFormatName);
         if (indexFirst < 0)
            indexFirst = 999;
         if (indexSecond < 0)
            indexSecond = 999;
         int diff = indexFirst - indexSecond;

         if (diff == 0)
            diff = first.name.compareNatural(second.name, false);

         return diff;
      }

      juce::StringArray mFormatOrder;
   };

   struct PluginNameSorter
   {
      int compareElements(const PluginDescription& first, const PluginDescription& second) const
      {
         return first.name.compareNatural(second.name, false);
      }
   };

   void GetAvailableVSTs(std::vector<PluginDescription>& vsts)
   {
      vsts.clear();
      static bool sFirstTime = true;
      if (sFirstTime)
      {
         auto file = juce::File(ofToDataPath("vst/found_vsts.xml"));
         if (file.existsAsFile())
         {
            auto xml = juce::parseXML(file);
            TheSynth->GetKnownPluginList().recreateFromXml(*xml);
         }
      }

      auto types = TheSynth->GetKnownPluginList().getTypes();
      std::string formatPreferenceOrder = UserPrefs.plugin_preference_order.Get();
      bool allowDupes = formatPreferenceOrder.empty();
      if (!allowDupes)
      {
         PluginFormatSorter formatSorter(formatPreferenceOrder);
         types.sort(formatSorter);
      }

      Array<PluginDescription> filtered;
      for (int i = 0; i < types.size(); ++i)
      {
         bool hasDupe = false;
         for (int j = 0; j < filtered.size(); ++j)
         {
            if (types[i].name == filtered[j].name)
            {
               hasDupe = true;
               break;
            }
         }

         if (!hasDupe || allowDupes)
            filtered.add(types[i]);
      }

      PluginNameSorter nameSorter;
      filtered.sort(nameSorter);
      for (int i = 0; i < filtered.size(); ++i)
         vsts.push_back(filtered[i]);

      //for (int i = 0; i < 2000; ++i)
      //   vsts.insert(vsts.begin(), std::string("c:/a+") + ofToString(gRandom()));

      //SortByLastUsed(vsts);

      //add a bunch of duplicates to the list, to simulate a user with many VSTs
      /*auto vstCopy = vsts;
      for (int i = 0; i < 40; ++i)
         vsts.insert(vsts.end(), vstCopy.begin(), vstCopy.end());*/

      sFirstTime = false;
   }

   void FillVSTList(DropdownList* list)
   {
      assert(list);
      std::vector<PluginDescription> vsts;
      GetAvailableVSTs(vsts);
      for (int i = 0; i < vsts.size(); ++i)
         list->AddLabel(vsts[i].createIdentifierString().toStdString(), i);
   }

   std::string GetVSTPath(std::string vstName)
   {
      if (juce::String(vstName).contains("/") || juce::String(vstName).contains("\\")) //already a path
         return vstName;

      vstName = GetFileNameWithoutExtension(vstName).toStdString();
      auto types = TheSynth->GetKnownPluginList().getTypes();
      for (int i = 0; i < types.size(); ++i)
      {
#if BESPOKE_MAC
         if (types[i].pluginFormatName == juce::AudioUnitPluginFormat::getFormatName())
            continue; //"fileOrIdentifier" is not a valid path, can't check
#endif
         juce::File vst(types[i].fileOrIdentifier);
         if (vst.getFileNameWithoutExtension().toStdString() == vstName)
            return types[i].fileOrIdentifier.toStdString();
      }

      return "";
   }

   juce::String cutOffIdHash(juce::String inputString)
   {
      juce::StringArray parts;
      parts.addTokens(inputString, "-", "");

      if (parts.size() >= 2)
      {
         parts.remove(parts.size() - 2);
         juce::String result = parts.joinIntoString("-");
         return result;
      }
      return inputString;
   }

   bool GetPluginDesc(juce::PluginDescription& desc, juce::String pluginId)
   {
      auto types = TheSynth->GetKnownPluginList().getTypes();
      auto cutId = cutOffIdHash(pluginId);

      for (int i = 0; i < types.size(); ++i)
      {
         if (types[i].createIdentifierString() == pluginId)
         {
            desc = types[i];
            return true;
         }
      }

      for (int i = 0; i < types.size(); ++i)
      {
         if (cutOffIdHash(types[i].createIdentifierString()) == cutId)
         {
            desc = types[i];
            return true;
         }
      }
      return false;
   }

   void GetRecentPlugins(std::vector<PluginDescription>& recentPlugins, int num)
   {
      recentPlugins.clear();
      juce::PluginDescription pluginDesc{};
      std::map<double, std::string> lastUsedTimes;
      int i = 0;

      if (juce::File(ofToDataPath("vst/recent_plugins.json")).existsAsFile())
      {
         ofxJSONElement root;
         root.open(ofToDataPath("vst/recent_plugins.json"));
         ofxJSONElement jsonList = root["vsts"];

         for (auto it = jsonList.begin(); it != jsonList.end(); ++it)
         {
            try
            {
               std::string id = it.key().asString();
               double time = jsonList[id].asDouble();
               lastUsedTimes.insert(std::make_pair(time, id));
            }
            catch (Json::LogicError& e)
            {
               TheSynth->LogEvent(__PRETTY_FUNCTION__ + std::string(" json error: ") + e.what(), kLogEventType_Error);
            }
         }
      }
      std::map<double, std::string>::reverse_iterator rit;
      rit = lastUsedTimes.rbegin();
      while (rit != lastUsedTimes.rend() && ++i <= num)
      {
         if (GetPluginDesc(pluginDesc, juce::String(rit->second)))
         {
            recentPlugins.push_back(pluginDesc);
         }
         ++rit;
      }
   }

   void SortByLastUsed(std::vector<juce::PluginDescription>& vsts)
   {
      std::map<std::string, double> lastUsedTimes;

      if (juce::File(ofToDataPath("vst/recent_plugins.json")).existsAsFile())
      {
         ofxJSONElement root;
         root.open(ofToDataPath("vst/recent_plugins.json"));
         ofxJSONElement jsonList = root["vsts"];

         for (auto it = jsonList.begin(); it != jsonList.end(); ++it)
         {
            try
            {
               std::string key = it.key().asString();
               lastUsedTimes[key] = jsonList[key].asDouble();
            }
            catch (Json::LogicError& e)
            {
               TheSynth->LogEvent(__PRETTY_FUNCTION__ + std::string(" json error: ") + e.what(), kLogEventType_Error);
            }
         }
      }

      std::sort(vsts.begin(), vsts.end(), [lastUsedTimes](juce::PluginDescription a, juce::PluginDescription b)
                {
                   auto itA = lastUsedTimes.find(a.createIdentifierString().toStdString());
                   auto itB = lastUsedTimes.find(b.createIdentifierString().toStdString());
                   double timeA = 0;
                   double timeB = 0;
                   if (itA != lastUsedTimes.end())
                      timeA = (*itA).second;
                   if (itB != lastUsedTimes.end())
                      timeB = (*itB).second;

                   if (timeA == timeB)
                      return a.name < b.name;

                   return timeA > timeB;
                });
   }
}

VSTPlugin::VSTPlugin()
: IAudioProcessor(gBufferSize)
{
   juce::File(ofToDataPath("vst")).createDirectory();
   juce::File(ofToDataPath("vst/presets")).createDirectory();

   mChannelModulations.resize(kGlobalModulationIdx + 1);

   mPluginName = "no plugin loaded";
}


void VSTPlugin::AddExtraOutputCable()
{
   AdditionalNoteCable* NewAdditionalCable = new AdditionalNoteCable();
   RollingBuffer* NewBuffer = new RollingBuffer(VIZ_BUFFER_SECONDS * gSampleRate);
   NewBuffer->SetNumChannels(2);

   PatchCableSource* NewCableSource = new PatchCableSource(this, kConnectionType_Audio);

   NewCableSource->SetOverrideVizBuffer(NewBuffer);
   NewAdditionalCable->SetPatchCableSource(NewCableSource);
   AddPatchCableSource(NewCableSource);

   mAdditionalOutCables.push_back(NewAdditionalCable);
   mAdditionalVizBuffers.push_back(NewBuffer);
   mAdditionalOutCableSources.push_back(NewCableSource);

   mModuleSaveData.SetInt("numAdditionalStereoOutputs", (int)mAdditionalOutCables.size());
}

void VSTPlugin::RemoveExtraOutputCable()
{
   if (static_cast<int>(mAdditionalOutCables.size()) == 0)
      return;

   int IndexToRemove = static_cast<int>(mAdditionalOutCables.size()) - 1;

   mAdditionalOutCables.pop_back();
   mAdditionalVizBuffers.pop_back();
   PatchCableSource* SourceToRemove = mAdditionalOutCableSources[IndexToRemove];
   RemovePatchCableSource(SourceToRemove);
   mAdditionalOutCableSources.pop_back();

   mModuleSaveData.SetInt("numAdditionalStereoOutputs", (int)mAdditionalOutCables.size());
}

void VSTPlugin::CreateUIControls()
{
   IDrawableModule::CreateUIControls();

   mVolSlider = new FloatSlider(this, "vol", 3, 3, 80, 15, &mVol, 0, 4);
   mOpenEditorButton = new ClickButton(this, "open", mVolSlider, kAnchor_Right_Padded);
   mPresetFileSelector = new DropdownList(this, "preset", 3, 21, &mPresetFileIndex, 142);
   mSavePresetFileButton = new ClickButton(this, "save as", -1, -1);
   mShowParameterDropdown = new DropdownList(this, "show parameter", 3, 56, &mShowParameterIndex, 190);
   mLoadParameterButton = new ClickButton(this, "load parameter", mShowParameterDropdown, kAnchor_Right);
   mPanicButton = new ClickButton(this, "panic", mOpenEditorButton, kAnchor_Right_Padded);
   mRemoveExtraOutputButton = new ClickButton(this, "  -  ", 83, 38);
   mAddExtraOutputButton = new ClickButton(this, " + ", mRemoveExtraOutputButton, kAnchor_Right);

   mPresetFileSelector->DrawLabel(true);
   mSavePresetFileButton->PositionTo(mPresetFileSelector, kAnchor_Right);

   mMidiOutCable = new AdditionalNoteCable();
   mMidiOutCable->SetPatchCableSource(new PatchCableSource(this, kConnectionType_Note));
   mMidiOutCable->GetPatchCableSource()->SetOverrideCableDir(ofVec2f(1, 0), PatchCableSource::Side::kRight);
   AddPatchCableSource(mMidiOutCable->GetPatchCableSource());
   mMidiOutCable->GetPatchCableSource()->SetManualPosition(206 - 10, 10);

   if (mPlugin)
   {
      CreateParameterSliders();
   }

   RecreateUIOutputCables();
}

void VSTPlugin::RecreateUIOutputCables()
{
   // Need to recalculate slider postitions so that the cablesources can be positioned correctly on load (before a draw call).
   constexpr int kRows = 25;
   int sliderCount{ 0 };
   for (const auto& slider : mParameterSliders)
   {
      if (slider.mSlider)
      {
         slider.mSlider->SetShowing(slider.mShowing);
         slider.mRemoveButton->SetShowing(slider.mShowing);
         if (slider.mShowing)
         {
            slider.mSlider->SetPosition(
            3 + (slider.mSlider->GetRect().width + slider.mRemoveButton->GetRect().width + 9) * (sliderCount / kRows),
            74 + 17 * (sliderCount % kRows));
            slider.mRemoveButton->SetPosition(
            6 + slider.mSlider->GetRect().width + (slider.mSlider->GetRect().width + slider.mRemoveButton->GetRect().width + 9) * (sliderCount / kRows),
            74 + 17 * (sliderCount % kRows));
            ++sliderCount;
         }
      }
   }

   float width, height;
   GetModuleDimensions(width, height);

   auto NumCables = static_cast<float>(mAdditionalOutCables.size()) + 1;
   float DesiredGap = width / (NumCables + 1);

   GetPatchCableSource()->SetManualSide(PatchCableSource::Side::kBottom);
   GetPatchCableSource()->SetManualPosition(DesiredGap, height + 3);

   mMidiOutCable->GetPatchCableSource()->SetManualPosition(width - 8, 10);

   for (int CableCount = 0; CableCount < NumCables - 1; CableCount++)
   {
      PatchCableSource* NewSource = mAdditionalOutCableSources[CableCount];
      NewSource->SetManualSide(PatchCableSource::Side::kBottom);
      NewSource->SetManualPosition(DesiredGap + (CableCount + 1) * DesiredGap, height + 3);
   }

   mLoadParameterButton->SetPosition(
   GetRect(true).width - 3 - mLoadParameterButton->GetRect(true).width,
   mShowParameterDropdown->GetRect(true).y);
}

VSTPlugin::~VSTPlugin()
{
}

void VSTPlugin::Exit()
{
   IDrawableModule::Exit();
   if (mWindow)
   {
      mWindow.reset();
   }
   if (mPlugin)
   {
      mPlugin.reset();
   }
}

std::string VSTPlugin::GetTitleLabel() const
{
   return GetPluginFormatName() + ": " + GetPluginName();
}

std::string VSTPlugin::GetPluginName() const
{
   return mPluginName;
}

std::string VSTPlugin::GetPluginFormatName() const
{
   if (mPlugin)
      return mPluginFormatName;
   return "plugin";
}

std::string VSTPlugin::GetPluginId() const
{
   if (mPlugin)
      return mPluginId;
   return "no plugin loaded";
}

void VSTPlugin::GetVSTFileDesc(std::string vstName, juce::PluginDescription& desc)
{
   std::string path = VSTLookup::GetVSTPath(vstName);

   auto types = TheSynth->GetKnownPluginList().getTypes();
   bool found = false;
   for (int i = 0; i < types.size(); ++i)
   {
      if (path == types[i].fileOrIdentifier)
      {
         found = true;
         desc = types[i];
         break;
      }
   }

   if (!found) //couldn't find the VST at this path. maybe its installation got moved, or the bespoke state was saved on a different computer. try to find a VST of the same name.
   {
      juce::String desiredVstName = juce::String(path).replaceCharacter('\\', '/').fromLastOccurrenceOf("/", false, false).upToFirstOccurrenceOf(".", false, false);
      for (int i = 0; i < types.size(); ++i)
      {
         juce::String thisVstName = juce::String(types[i].fileOrIdentifier).replaceCharacter('\\', '/').fromLastOccurrenceOf("/", false, false).upToFirstOccurrenceOf(".", false, false);
         if (thisVstName == desiredVstName)
         {
            desc = types[i];
            break;
         }
      }
   }
}

void VSTPlugin::SetVST(juce::PluginDescription pluginDesc)
{
   ofLog() << "loading Plugin: " << pluginDesc.name << "; Format: " << pluginDesc.pluginFormatName << "; ID: " << pluginDesc.uniqueId;

   juce::String pluginId = pluginDesc.createIdentifierString();
   std::string strPluginId = pluginId.toStdString();

   if (strPluginId != kInvalidPluginId)
      mModuleSaveData.SetString("pluginId", strPluginId);
   else
      mModuleSaveData.SetString("pluginId", "none");

   //mark VST as used
   if (strPluginId != kInvalidPluginId)
   {
      ofxJSONElement root;
      root.open(ofToDataPath("vst/recent_plugins.json"));

      auto time = juce::Time::getCurrentTime();
      root["vsts"][strPluginId] = (double)time.currentTimeMillis();

      root.save(ofToDataPath("vst/recent_plugins.json"), true);
   }

   if (mPlugin != nullptr && dynamic_cast<juce::AudioPluginInstance*>(mPlugin.get())->getPluginDescription().matchesIdentifierString(pluginId))
      return; //this VST is already loaded! we're all set

   if (mPlugin != nullptr && mWindow != nullptr)
   {
      VSTWindow* window = mWindow.release();
      delete window;
   }

   LoadVST(pluginDesc);
}

void VSTPlugin::LoadVST(juce::PluginDescription desc)
{
   mPluginReady = false;

   mVSTMutex.lock();
   juce::String errorMessage;
   mPlugin = TheSynth->GetAudioPluginFormatManager().createPluginInstance(desc, gSampleRate, gBufferSize, errorMessage);
   if (mPlugin != nullptr)
   {
      mPlugin->enableAllBuses();
      mPlugin->addListener(this);

      mNumInputChannels = mPlugin->getTotalNumInputChannels();
      mNumOutputChannels = mPlugin->getTotalNumOutputChannels();
      ofLog() << "vst channel - inputs: " << mNumInputChannels << " x outputs: " << mNumOutputChannels;

      auto layouts = mPlugin->getBusesLayout();
      mNumInBuses = layouts.inputBuses.size();
      mNumOutBuses = layouts.outputBuses.size();
      mPlugin->enableAllBuses();
      ofLog() << "vst layout  - inputs: " << layouts.inputBuses.size() << " x outputs: " << layouts.outputBuses.size();

      mPlugin->prepareToPlay(gSampleRate, gBufferSize);
      mPlugin->setPlayHead(&mPlayhead);

      mPluginName = mPlugin->getName().toStdString();
      mPluginFormatName = ofToString(desc.pluginFormatName.toLowerCase());
      mPluginId = GetPluginName() + "_" + ofToString(desc.uniqueId);

      CreateParameterSliders();

      RefreshPresetFiles();

      mPluginReady = true;
   }
   else
   {
      TheSynth->LogEvent("error loading VST: " + errorMessage.toStdString(), kLogEventType_Error);

      if (mModuleSaveData.HasProperty("pluginId") && mModuleSaveData.GetString("pluginId").length() > 0)
         mPluginName = mModuleSaveData.GetString("pluginId") + " (not loaded)";
   }
   mVSTMutex.unlock();
}

void VSTPlugin::CreateParameterSliders()
{
   assert(mPlugin);

   for (auto& slider : mParameterSliders)
   {
      if (slider.mSlider)
      {
         slider.mSlider->SetShowing(false);
         slider.mRemoveButton->SetShowing(false);
         RemoveUIControl(slider.mSlider);
         RemoveUIControl(slider.mRemoveButton);
         slider.mSlider->Delete();
         slider.mRemoveButton->Delete();
         slider.mSlider = nullptr;
         slider.mRemoveButton = nullptr;
      }
   }
   mParameterSliders.clear();

   mShowParameterDropdown->Clear();

   const auto& parameters = mPlugin->getParameters();

   int numParameters = parameters.size();
   mParameterSliders.resize(numParameters);
   for (int i = 0; i < numParameters; ++i)
   {

      mParameterSliders[i].mOwner = this;
      mParameterSliders[i].mValue = parameters[i]->getValue();
      mParameterSliders[i].mParameter = parameters[i];
      mParameterSliders[i].mDisplayName = parameters[i]->getName(64).toStdString();
      HostedAudioProcessorParameter* parameterWithId = dynamic_cast<HostedAudioProcessorParameter*>(parameters[i]);
      if (parameterWithId)
         mParameterSliders[i].mID = "paramid_" + parameterWithId->getParameterID().toStdString();
      else
         mParameterSliders[i].mID = "param_" + ofToString(parameters[i]->getParameterIndex());
      mParameterSliders[i].mShowing = false;
      if (numParameters <= kMaxParametersInDropdown) //only show parameters in list if there are a small number. if there are many, make the user adjust them in the VST before they can be controlled
      {
         mShowParameterDropdown->AddLabel(mParameterSliders[i].mDisplayName, i);
         mParameterSliders[i].mInSelectorList = true;
      }
      else
      {
         mParameterSliders[i].mInSelectorList = false;
      }
   }
   // Move output cables
   RecreateUIOutputCables();
}

void VSTPlugin::Poll()
{
   if (mRescanParameterNames)
   {
      mRescanParameterNames = false;
      const auto& parameters = mPlugin->getParameters();

      const int numParameters = MIN((int)mParameterSliders.size(), (int)parameters.size());
      for (int i = 0; i < numParameters; ++i)
      {
         mParameterSliders[i].mDisplayName = parameters[i]->getName(64).toStdString();
         if (mParameterSliders[i].mSlider != nullptr)
            mParameterSliders[i].mSlider->SetOverrideDisplayName(mParameterSliders[i].mDisplayName);
      }

      if (numParameters <= kMaxParametersInDropdown) // update the dropdown in this case
      {
         mShowParameterDropdown->Clear();
         for (int i = 0; i < numParameters; ++i)
         {
            mShowParameterDropdown->AddLabel(mParameterSliders[i].mDisplayName, i);
            mParameterSliders[i].mInSelectorList = true;
         }
      }
   }

   for (auto& parameterSlider : mParameterSliders)
      parameterSlider.mValue = parameterSlider.mParameter->getValue();

   if (mChangeGestureParameterIndex != -1)
   {
      if (mChangeGestureParameterIndex < (int)mParameterSliders.size() && !mParameterSliders[mChangeGestureParameterIndex].mInSelectorList)
      {
         mShowParameterDropdown->AddLabel(mParameterSliders[mChangeGestureParameterIndex].mDisplayName, mChangeGestureParameterIndex);
         mParameterSliders[mChangeGestureParameterIndex].mInSelectorList = true;
      }
      mChangeGestureParameterIndex = -1;
   }

   if (mWantOpenVstWindow)
   {
      mWantOpenVstWindow = false;
      if (mPlugin != nullptr)
      {
         if (mWindow == nullptr)
            mWindow = std::unique_ptr<VSTWindow>(VSTWindow::CreateVSTWindow(this, VSTWindow::Normal));
         mWindow->ShowWindow();
      }
   }

   if (mPresetFileUpdateQueued)
   {
      mPresetFileUpdateQueued = false;
      if (mPresetFileIndex >= 0 && mPresetFileIndex < (int)mPresetFilePaths.size())
      {
         mLastLoadedPresetFilePath = mPresetFilePaths[mPresetFileIndex];
         File resourceFile = File(mLastLoadedPresetFilePath);

         if (!resourceFile.existsAsFile())
         {
            DBG("File doesn't exist ...");
            return;
         }

         std::unique_ptr<FileInputStream> input(resourceFile.createInputStream());

         if (!input->openedOk())
         {
            DBG("Failed to open file");
            return;
         }

         int rev = input->readInt();

         int64 vstStateSize = input->readInt64();
         char* vstState = new char[vstStateSize];
         //TODO(Ryan) is vstStateSize ever bigger than an int, so we shouldn't cast away the int64-ness?
         input->read(vstState, (int)vstStateSize);
         mPlugin->setStateInformation(vstState, (int)vstStateSize);

         int64 vstProgramStateSize = input->readInt64();
         if (vstProgramStateSize > 0)
         {
            char* vstProgramState = new char[vstProgramStateSize];
            //TODO(Ryan) is vstProgramStateSize ever bigger than an int, so we shouldn't cast away the int64-ness?
            input->read(vstProgramState, (int)vstProgramStateSize);
            mPlugin->setCurrentProgramStateInformation(vstProgramState, (int)vstProgramStateSize);
         }

         if (rev >= 2 && mModuleSaveData.GetBool("preset_file_sets_params"))
         {
            int numParamsShowing = input->readInt();
            for (auto& param : mParameterSliders)
               param.mShowing = false;
            for (int i = 0; i < numParamsShowing; ++i)
            {
               int index = input->readInt();
               if (index < mParameterSliders.size())
               {
                  mParameterSliders[index].mShowing = true;
                  if (mParameterSliders[index].mSlider == nullptr)
                     mParameterSliders[index].MakeSlider();
               }
            }
            // Move output cables
            RecreateUIOutputCables();
         }
      }
   }
}
void VSTPlugin::audioProcessorChanged(juce::AudioProcessor* processor, const ChangeDetails& details)
{
   if (details.parameterInfoChanged)
   {
      mRescanParameterNames = true;
   }
}

void VSTPlugin::audioProcessorParameterChangeGestureBegin(juce::AudioProcessor* processor, int parameterIndex)
{
   //set this parameter so we can check it in Poll()
   mChangeGestureParameterIndex = parameterIndex;
}

void VSTPlugin::Process(double time)
{
   if (!mPluginReady)
   {
      //bypass
      GetBuffer()->SetNumActiveChannels(2);
      SyncBuffers();
      for (int ch = 0; ch < GetBuffer()->NumActiveChannels(); ++ch)
      {
         if (GetTarget())
            Add(GetTarget()->GetBuffer()->GetChannel(ch), GetBuffer()->GetChannel(ch), GetBuffer()->BufferSize());
         GetVizBuffer()->WriteChunk(GetBuffer()->GetChannel(ch), GetBuffer()->BufferSize(), ch);
      }

      GetBuffer()->Clear();
   }

#if BESPOKE_LINUX //HACK: weird race condition, which this seems to fix for now
   if (mPlugin == nullptr)
      return;
#endif

   PROFILER(VSTPlugin);

   if (mWantsAddExtraOutput || mWantsRemoveExtraOutput)
   {
      auto layouts = mPlugin->getBusesLayout();

      if (mWantsAddExtraOutput && ((mAdditionalOutCables.size() < (maxStereoOutputChannels - 1))))
      {
         AddExtraOutputCable();
      }
      else if (mWantsRemoveExtraOutput)
      {
         RemoveExtraOutputCable();
      }

      mWantsAddExtraOutput = false;
      mWantsRemoveExtraOutput = false;

      RecreateUIOutputCables();
   }

   int inputChannels = MAX(2, mNumInputChannels);
   int outputChannels = MAX(2, mNumOutputChannels);
   ChannelBuffer* AllChannelsBuffer = GetBuffer();

   AllChannelsBuffer->SetNumActiveChannels(inputChannels);
   SyncBuffers();

   if (mWantLoadParameters)
   {
      auto numParametersAdded{ 0 };
      bool addSliders = GetKeyModifiers() & kModifier_Shift;
      for (int i = 0; i < mParameterSliders.size(); ++i)
      {
         if (mParameterSliders[i].mInSelectorList == false)
         {
            mShowParameterDropdown->AddLabel(mParameterSliders[i].mDisplayName, i);
            mParameterSliders[i].mInSelectorList = true;
            if (addSliders && mParameterSliders[i].mSlider == nullptr)
            {
               mParameterSliders[i].MakeSlider();
               mParameterSliders[i].mShowing = true;
            }
            numParametersAdded++;
         }
         if (numParametersAdded >= 100)
            break;
      }
      mWantLoadParameters = false;
      if (numParametersAdded > 0)
         RecreateUIOutputCables();
   }

   /*
    * Multi-out VSTs which can't disable those outputs will expect *something* in the
    * buffer even though we don't read it.
    * 
    * Before processing audio in the VST we copy the Bespoke inputs into a juce buffer, the buffer needs enough mono channels to cover inputs and outputs
    * So 2 inputs and 4 outputs means we need 4 channels total (the first two for inputs will be overwritten with the outputs)
    */
   int juceBufferChannelCount = MAX(MAX(inputChannels * mNumInBuses, outputChannels * mNumOutBuses), 2); // how much to allocate in the juce::AudioBuffer

   const int kSafetyMaxStereoChannels = VSTPlugin::maxStereoOutputChannels; //hitting a crazy issue (memory stomp?) where numchannels is getting blown out sometimes

   int bufferSize = GetBuffer()->BufferSize();
   assert(bufferSize == gBufferSize);

   juce::AudioBuffer<float> buffer(juceBufferChannelCount, bufferSize);
   for (int i = 0; i < inputChannels && i < kSafetyMaxStereoChannels; ++i)
      buffer.copyFrom(i, 0, AllChannelsBuffer->GetChannel(MIN(i, GetBuffer()->NumActiveChannels() - 1)), AllChannelsBuffer->BufferSize());

   IAudioReceiver* target = GetTarget();

   if (mEnabled && mPlugin != nullptr)
   {
      mVSTMutex.lock();

      ComputeSliders(0);

      if (mWantRemoveSlider)
      {
         mParameterSliders[mWantRemoveSliderIndex].mSlider->SetShowing(false);
         mParameterSliders[mWantRemoveSliderIndex].mRemoveButton->SetShowing(false);
         RemoveUIControl(mParameterSliders[mWantRemoveSliderIndex].mSlider);
         RemoveUIControl(mParameterSliders[mWantRemoveSliderIndex].mRemoveButton);
         mParameterSliders[mWantRemoveSliderIndex].mSlider->Delete();
         mParameterSliders[mWantRemoveSliderIndex].mRemoveButton->Delete();
         mParameterSliders[mWantRemoveSliderIndex].mSlider = nullptr;
         mParameterSliders[mWantRemoveSliderIndex].mRemoveButton = nullptr;
         mParameterSliders[mWantRemoveSliderIndex].mInSelectorList = false;
         mWantRemoveSlider = false;
         RecreateUIOutputCables();
      }

      {
         const juce::ScopedLock lock(mMidiInputLock);

         for (int i = 0; i < mChannelModulations.size(); ++i)
         {
            ChannelModulations& mod = mChannelModulations[i];
            int channel = i + 1;
            if (i == kGlobalModulationIdx)
               channel = 1;

            if (mUseVoiceAsChannel == false)
               channel = mChannel;

            float bend = mod.mModulation.pitchBend ? mod.mModulation.pitchBend->GetValue(0) : ModulationParameters::kDefaultPitchBend;
            if (bend != mod.mLastPitchBend)
            {
               mod.mLastPitchBend = bend;
               mMidiBuffer.addEvent(juce::MidiMessage::pitchWheel(channel, (int)ofMap(bend, -mPitchBendRange, mPitchBendRange, 0, 16383, K(clamp))), 0);
            }
            float modWheel = mod.mModulation.modWheel ? mod.mModulation.modWheel->GetValue(0) : ModulationParameters::kDefaultModWheel;
            if (modWheel != mod.mLastModWheel)
            {
               mod.mLastModWheel = modWheel;
               mMidiBuffer.addEvent(juce::MidiMessage::controllerEvent(channel, mModwheelCC, ofClamp(modWheel * 127, 0, 127)), 0);
            }
            float pressure = mod.mModulation.pressure ? mod.mModulation.pressure->GetValue(0) : ModulationParameters::kDefaultPressure;
            if (pressure != mod.mLastPressure)
            {
               mod.mLastPressure = pressure;
               mMidiBuffer.addEvent(juce::MidiMessage::channelPressureChange(channel, ofClamp(pressure * 127, 0, 127)), 0);
            }
         }

         /*if (!mMidiBuffer.isEmpty())
         {
            ofLog() << mMidiBuffer.getFirstEventTime() << " " << mMidiBuffer.getLastEventTime();
         }*/

         mMidiBuffer.addEvents(mFutureMidiBuffer, 0, mFutureMidiBuffer.getLastEventTime() + 1, 0);
         mFutureMidiBuffer.clear();
         mFutureMidiBuffer.addEvents(mMidiBuffer, gBufferSize, mMidiBuffer.getLastEventTime() - gBufferSize + 1, -gBufferSize);
         mMidiBuffer.clear(gBufferSize, mMidiBuffer.getLastEventTime() + 1);

         if (mWantsPanic)
         {
            mWantsPanic = false;

            mMidiBuffer.clear();
            for (int channel = 1; channel <= kMaxJuceMidiStereoChannels; ++channel)
               mMidiBuffer.addEvent(juce::MidiMessage::allNotesOff(channel), 0);
            for (int channel = 1; channel <= kMaxJuceMidiStereoChannels; ++channel)
               mMidiBuffer.addEvent(juce::MidiMessage::allSoundOff(channel), 1);
         }

         mPlugin->processBlock(buffer, mMidiBuffer);

         if (!mMidiBuffer.isEmpty())
         {
            auto midiIt = mMidiBuffer.begin();
            while (midiIt != mMidiBuffer.end())
            {
               auto msg = (*midiIt).getMessage();
               auto tMidi = time + (*midiIt).samplePosition * gInvSampleRateMs;
               if (msg.isNoteOn())
               {
                  mMidiOutCable->PlayNoteOutput(NoteMessage(tMidi, msg.getNoteNumber(), msg.getVelocity()));
               }
               else if (msg.isNoteOff())
               {
                  mMidiOutCable->PlayNoteOutput(NoteMessage(tMidi, msg.getNoteNumber(), 0));
               }
               else if (msg.isController())
               {
                  mMidiOutCable->SendCCOutput(msg.getControllerNumber(), msg.getControllerValue());
               }
               midiIt++;
            }
         }

         mMidiBuffer.clear();
      }
      mVSTMutex.unlock();

      AllChannelsBuffer->Clear();

      int numChannels = 2 + ((int)mAdditionalOutCableSources.size() * 2);

      if (numChannels != mLastNumChannels)
      {
         AllChannelsBuffer->SetNumActiveChannels(numChannels);
         AllChannelsBuffer->SetMaxAllowedChannels(numChannels);
         mLastNumChannels = numChannels;
      }

      /*
       * Until we support multi output we end up with this requirement that
       * the output is at most stereo. This stops mis-behaving plugins which
       * output the full buffer set from copying that onto the output.
       * (Ahem: Surge 1.9)
       */
      int nSingleChannelsToCopy = MIN(AllChannelsBuffer->NumActiveChannels(), buffer.getNumChannels());
      for (int sourceChannel = 0; sourceChannel < nSingleChannelsToCopy && sourceChannel < (kSafetyMaxStereoChannels * 2); ++sourceChannel)
      {
         int sourceStereoChannel = sourceChannel % 2;
         int stereoIndex = floor(sourceChannel / 2);

         IAudioReceiver* CurrentTargetAudioReceiver;
         RollingBuffer* CurrentVizBuffer;

         if (stereoIndex == 0)
         {
            // Default behaviour for single output VST plugins, use the IAudioSource defined target and default VizBuffer
            CurrentTargetAudioReceiver = target;
            CurrentVizBuffer = GetVizBuffer();
         }
         else
         {
            // This is a multi out VST, we need to use the additional Vis Buffer and set the target to the node this pat
            CurrentTargetAudioReceiver = mAdditionalOutCables[stereoIndex - 1]->GetPatchCableSource()->GetAudioReceiver();
            CurrentVizBuffer = mAdditionalVizBuffers[stereoIndex - 1];
         }

         // Copy the output from juce into our own buffer
         int numMonoSamples = buffer.getNumSamples();
         for (int sampleIndex = 0; sampleIndex < numMonoSamples; ++sampleIndex)
         {
            auto temp_buffer = AllChannelsBuffer->GetChannel(sourceChannel);
            if (temp_buffer == nullptr)
               continue;
            temp_buffer[sampleIndex] += buffer.getSample(sourceChannel, sampleIndex) * mVol;
         }

         // Copy the outputs from the single buffer into our multiple output buffers
         if (CurrentTargetAudioReceiver)
         {
            ChannelBuffer* targetBuffer = CurrentTargetAudioReceiver->GetBuffer();
            targetBuffer->SetNumActiveChannels(2);

            float* Destination = targetBuffer->GetChannel(sourceStereoChannel);
            float* Source = AllChannelsBuffer->GetChannel(sourceChannel);

            // Add the samples to the destination Audio Receiver buffer
            Add(
            Destination,
            Source,
            AllChannelsBuffer->BufferSize());

            // Also write the samples to the Visualizer Buffer for this cable output
            CurrentVizBuffer->WriteChunk(Source, targetBuffer->BufferSize(), sourceStereoChannel);
         }
      }
   }
   else
   {
      //bypass
      for (int ch = 0; ch < GetBuffer()->NumActiveChannels(); ++ch)
      {
         if (target)
            Add(
            target->GetBuffer()->GetChannel(ch),
            AllChannelsBuffer->GetChannel(ch),
            AllChannelsBuffer->BufferSize());
         GetVizBuffer()->WriteChunk(AllChannelsBuffer->GetChannel(ch), AllChannelsBuffer->BufferSize(), ch);
      }
   }

   AllChannelsBuffer->Clear();
}

void VSTPlugin::PlayNote(NoteMessage note)
{
   if (!mPluginReady || mPlugin == nullptr)
      return;

   if (!mEnabled)
      return;

   if (note.pitch < 0 || note.pitch > 127)
      return;

   int channel = note.voiceIdx + 1;
   if (note.voiceIdx == -1)
      channel = 1;

   const juce::ScopedLock lock(mMidiInputLock);

   int sampleNumber = (note.time - gTime) * gSampleRateMs;
   //ofLog() << sampleNumber;

   if (note.velocity > 0)
   {
      mMidiBuffer.addEvent(juce::MidiMessage::noteOn(mUseVoiceAsChannel ? channel : mChannel, note.pitch, (uint8)note.velocity), sampleNumber);
      //ofLog() << "+ vst note on: " << (mUseVoiceAsChannel ? channel : mChannel) << " " << pitch << " " << (uint8)velocity;
   }
   else
   {
      mMidiBuffer.addEvent(juce::MidiMessage::noteOff(mUseVoiceAsChannel ? channel : mChannel, note.pitch), sampleNumber);
      //ofLog() << "- vst note off: " << (mUseVoiceAsChannel ? channel : mChannel) << " " << pitch;
   }

   int modIdx = note.voiceIdx;
   if (note.voiceIdx == -1)
      modIdx = kGlobalModulationIdx;

   mChannelModulations[modIdx].mModulation = note.modulation;
}

void VSTPlugin::SendCC(int control, int value, int voiceIdx /*=-1*/)
{
   if (!mPluginReady || mPlugin == nullptr)
      return;

   if (control < 0 || control > 127)
      return;

   int channel = voiceIdx + 1;
   if (voiceIdx == -1)
      channel = 1;

   const juce::ScopedLock lock(mMidiInputLock);

   mMidiBuffer.addEvent(juce::MidiMessage::controllerEvent((mUseVoiceAsChannel ? channel : mChannel), control, (uint8)value), 0);
}

void VSTPlugin::SendMidi(const juce::MidiMessage& message)
{
   if (!mPluginReady || mPlugin == nullptr)
      return;

   const juce::ScopedLock lock(mMidiInputLock);

   mMidiBuffer.addEvent(message, 0);
}

void VSTPlugin::SetEnabled(bool enabled)
{
   mEnabled = enabled;
}

void VSTPlugin::DrawModule()
{
   if (Minimized() || IsVisible() == false)
      return;

   mVolSlider->Draw();
   mPresetFileSelector->Draw();
   mSavePresetFileButton->Draw();
   mOpenEditorButton->Draw();
   mPanicButton->Draw();
   mRemoveExtraOutputButton->Draw();
   mAddExtraOutputButton->Draw();
   mShowParameterDropdown->Draw();

   ofPushStyle();
   ofSetColor(IDrawableModule::GetColor(kModuleCategory_Note));
   DrawTextRightJustify("midi out:", GetRect().width - 15, 14);
   ofPopStyle();

   ofPushStyle();
   ofSetColor(IDrawableModule::GetColor(kModuleCategory_Synth));
   DrawTextNormal("extra outputs:", 3, 50);
   ofPopStyle();

   ofPushStyle();
   ofSetColor(IDrawableModule::GetColor(kModuleCategory_Synth), 75);
   DrawTextRightJustify(ofToString(mParameterSliders.size()), GetRect().width - 3, 67);
   ofPopStyle();

   for (const auto& slider : mParameterSliders)
      if (slider.mSlider && slider.mShowing)
      {
         slider.mSlider->Draw();
         slider.mRemoveButton->Draw();
      }
}


void VSTPlugin::GetModuleDimensions(float& width, float& height)
{
   width = 236;
   height = 76;
   for (auto& slider : mParameterSliders)
   {
      if (slider.mSlider && slider.mShowing)
      {
         width = MAX(width, slider.mSlider->GetRect(true).x + slider.mSlider->GetRect(true).width + 23);
         height = MAX(height, slider.mSlider->GetRect(true).y + slider.mSlider->GetRect(true).height + 3);
      }
   }
}

void VSTPlugin::OnVSTWindowClosed()
{
   mWindow.release();
}

std::vector<IUIControl*> VSTPlugin::ControlsToIgnoreInSaveState() const
{
   std::vector<IUIControl*> ignore;
   ignore.push_back(mPresetFileSelector);
   return ignore;
}

void VSTPlugin::DropdownUpdated(DropdownList* list, int oldVal, double time)
{
   if (list == mPresetFileSelector)
      mPresetFileUpdateQueued = true;

   if (list == mShowParameterDropdown)
   {
      mParameterSliders[mShowParameterIndex].mShowing = true;
      if (mParameterSliders[mShowParameterIndex].mSlider == nullptr)
         mParameterSliders[mShowParameterIndex].MakeSlider();
      mParameterSliders[mShowParameterIndex].mInSelectorList = true;
      mShowParameterIndex = -1;
      mTemporarilyDisplayedParamIndex = -1;
      // Move output cables
      RecreateUIOutputCables();
   }
}

void VSTPlugin::FloatSliderUpdated(FloatSlider* slider, float oldVal, double time)
{
   for (const auto& parameterSlider : mParameterSliders)
   {
      if (parameterSlider.mSlider == slider)
      {
         parameterSlider.mParameter->setValue(parameterSlider.mValue);
         break;
      }
   }
}

void VSTPlugin::IntSliderUpdated(IntSlider* slider, int oldVal, double time)
{
}

void VSTPlugin::CheckboxUpdated(Checkbox* checkbox, double time)
{
}

void VSTPlugin::ButtonClicked(ClickButton* button, double time)
{
   if (button == mOpenEditorButton)
   {
      mWantOpenVstWindow = true;
      return;
   }

   if (button == mPanicButton)
   {
      mWantsPanic = true;
      return;
   }

   if (button == mAddExtraOutputButton)
   {
      mWantsAddExtraOutput = true;
      return;
   }

   if (button == mRemoveExtraOutputButton)
   {
      mWantsRemoveExtraOutput = true;
      return;
   }

   if (button == mLoadParameterButton)
   {
      if (GetKeyModifiers() & kModifier_Command)
      {
         CreateParameterSliders();
         return;
      }
      mWantLoadParameters = true;
      return;
   }

   if (button == mSavePresetFileButton && mPlugin != nullptr)
   {
      juce::File(ofToDataPath("vst/presets/" + GetPluginId())).createDirectory();
      FileChooser chooser("Save preset as...", File(ofToDataPath("vst/presets/" + GetPluginId() + "/preset.vstp")), "*.vstp", true, false, TheSynth->GetFileChooserParent());
      if (chooser.browseForFileToSave(true))
      {
         std::string path = chooser.getResult().getFullPathName().toStdString();

         File resourceFile(path);
         TemporaryFile tempFile(resourceFile);

         {
            FileOutputStream output(tempFile.getFile());

            if (!output.openedOk())
            {
               DBG("FileOutputStream didn't open correctly ...");
               return;
            }

            juce::MemoryBlock vstState;
            mPlugin->getStateInformation(vstState);
            juce::MemoryBlock vstProgramState;
            mPlugin->getCurrentProgramStateInformation(vstProgramState);

            output.writeInt(GetModuleSaveStateRev());
            output.writeInt64(vstState.getSize());
            output.write(vstState.getData(), vstState.getSize());
            output.writeInt64(vstProgramState.getSize());
            if (vstProgramState.getSize() > 0)
               output.write(vstProgramState.getData(), vstProgramState.getSize());

            std::vector<int> exposedParams;
            for (int i = 0; i < (int)mParameterSliders.size(); ++i)
            {
               if (mParameterSliders[i].mShowing)
                  exposedParams.push_back(i);
            }
            output.writeInt((int)exposedParams.size());
            for (auto& i : exposedParams)
               output.writeInt(i);

            output.flush(); // (called explicitly to force an fsync on posix)

            if (output.getStatus().failed())
            {
               DBG("An error occurred in the FileOutputStream");
               return;
            }
         }

         bool success = tempFile.overwriteTargetFileWithTemporary();
         if (!success)
         {
            DBG("An error occurred writing the file");
            return;
         }

         RefreshPresetFiles();

         for (size_t i = 0; i < mPresetFilePaths.size(); ++i)
         {
            if (mPresetFilePaths[i] == path)
            {
               mPresetFileIndex = (int)i;
               break;
            }
         }
      }
   }

   for (int i = 0; i < mParameterSliders.size(); ++i)
   {
      if (mParameterSliders[i].mRemoveButton == button)
      {
         mWantRemoveSlider = true;
         mWantRemoveSliderIndex = i;
      }
   }
}

void VSTPlugin::DropdownClicked(DropdownList* list)
{
   if (list == mPresetFileSelector)
      RefreshPresetFiles();
}

void VSTPlugin::RefreshPresetFiles()
{
   if (mPlugin == nullptr)
      return;

   juce::File(ofToDataPath("vst/presets/" + GetPluginId())).createDirectory();
   mPresetFilePaths.clear();
   mPresetFileSelector->Clear();
   juce::Array<juce::File> fileList;
   for (const auto& entry : RangedDirectoryIterator{ juce::File{ ofToDataPath("vst/presets/" + GetPluginId()) }, false, "*.vstp" })
   {
      fileList.add(entry.getFile());
   }
   fileList.sort();
   for (const auto& file : fileList)
   {
      int index = (int)mPresetFilePaths.size();
      mPresetFileSelector->AddLabel(file.getFileNameWithoutExtension().toStdString(), index);
      std::string filePath = file.getFullPathName().toStdString();
      mPresetFilePaths.push_back(filePath);
      if (filePath == mLastLoadedPresetFilePath)
         mPresetFileIndex = index;
   }
}

void VSTPlugin::SaveLayout(ofxJSONElement& moduleInfo)
{
   moduleInfo["parameterversion"] = 1;
}

void VSTPlugin::LoadLayout(const ofxJSONElement& moduleInfo)
{
   mModuleSaveData.LoadString("pluginId", moduleInfo, "", VSTLookup::FillVSTList);

   mModuleSaveData.LoadString("target", moduleInfo);

   mModuleSaveData.LoadInt("channel", moduleInfo, 1, 0, 16);
   mModuleSaveData.LoadBool("usevoiceaschannel", moduleInfo, false);
   mModuleSaveData.LoadFloat("pitchbendrange", moduleInfo, 2, 1, 96, K(isTextField));
   mModuleSaveData.LoadInt("modwheelcc(1or74)", moduleInfo, 1, 0, 127, K(isTextField));
   mModuleSaveData.LoadInt("numAdditionalStereoOutputs", moduleInfo, 0, 0, VSTPlugin::maxStereoOutputChannels, false);

   mModuleSaveData.LoadBool("preset_file_sets_params", moduleInfo, true);

   if (!moduleInfo["vst"].isNull())
      mOldVstPath = moduleInfo["vst"].asString();
   else
      mOldVstPath = "";

   if (!IsSpawningOnTheFly(moduleInfo))
   {
      if (!moduleInfo["parameterversion"].isNull())
         mParameterVersion = moduleInfo["parameterversion"].asInt();
      else
         mParameterVersion = 0;
   }

   SetUpFromSaveData();
}

void VSTPlugin::SetUpFromSaveData()
{
   juce::PluginDescription pluginDesc;

   if (mModuleSaveData.HasProperty("pluginId") && mModuleSaveData.GetString("pluginId") != "")
   {
      auto pluginId = juce::String(mModuleSaveData.GetString("pluginId"));
      if (VSTLookup::GetPluginDesc(pluginDesc, pluginId))
      {
         TheSynth->LogEvent("Plugin with " + pluginId.toStdString() + " id not found", kLogEventType_Error);
      }
   }
   else if (!mOldVstPath.empty())
   {
      GetVSTFileDesc(mOldVstPath, pluginDesc);
   }

   SetVST(pluginDesc);

   SetTarget(TheSynth->FindModule(mModuleSaveData.GetString("target")));

   // Add VST output pins
   int numAdditionalStereoOutputs = mModuleSaveData.GetInt("numAdditionalStereoOutputs");
   for (int newOutputCount = 0; newOutputCount < numAdditionalStereoOutputs; newOutputCount++)
   {
      AddExtraOutputCable();
   }

   mChannel = mModuleSaveData.GetInt("channel");
   mUseVoiceAsChannel = mModuleSaveData.GetBool("usevoiceaschannel");
   mPitchBendRange = mModuleSaveData.GetFloat("pitchbendrange");
   mModwheelCC = mModuleSaveData.GetInt("modwheelcc(1or74)");
}

void VSTPlugin::SaveState(FileStreamOut& out)
{
   out << GetModuleSaveStateRev();

   if (mPlugin)
   {
      out << true;
      juce::MemoryBlock vstState;
      mPlugin->getStateInformation(vstState);
      out << (int)vstState.getSize();
      out.WriteGeneric(vstState.getData(), (int)vstState.getSize());

      juce::MemoryBlock vstProgramState;
      mPlugin->getCurrentProgramStateInformation(vstProgramState);
      out << (int)vstProgramState.getSize();
      if (vstProgramState.getSize() > 0)
         out.WriteGeneric(vstProgramState.getData(), (int)vstProgramState.getSize());

      std::vector<int> exposedParams;
      for (int i = 0; i < (int)mParameterSliders.size(); ++i)
      {
         if (mParameterSliders[i].mShowing)
            exposedParams.push_back(i);
      }
      out << (int)exposedParams.size();
      for (auto& i : exposedParams)
         out << i;
   }
   else
   {
      out << false;
   }

   IDrawableModule::SaveState(out);

   out << mLastLoadedPresetFilePath;
}

void VSTPlugin::LoadState(FileStreamIn& in, int rev)
{
   if (rev >= 3)
   {
      LoadVSTFromSaveData(in, rev);
   }
   else
   {
      //make all sliders, like we used to, so that the controls can load correctly
      for (auto& parameter : mParameterSliders)
      {
         if (parameter.mSlider == nullptr)
            parameter.MakeSlider();
      }
   }

   IDrawableModule::LoadState(in, rev);

   if (ModularSynth::sLoadingFileSaveStateRev < 423)
      in >> rev;
   LoadStateValidate(rev <= GetModuleSaveStateRev());

   if (rev < 3)
      LoadVSTFromSaveData(in, rev);

   if (rev >= 4)
      in >> mLastLoadedPresetFilePath;

   RecreateUIOutputCables();
   RefreshPresetFiles();
}

void VSTPlugin::LoadVSTFromSaveData(FileStreamIn& in, int rev)
{
   bool hasPlugin;
   in >> hasPlugin;
   if (hasPlugin)
   {
      int vstStateSize;
      in >> vstStateSize;
      char* vstState = new char[vstStateSize];
      in.ReadGeneric(vstState, vstStateSize);

      int vstProgramStateSize = 0;
      if (rev >= 1)
         in >> vstProgramStateSize;
      char* vstProgramState = new char[vstProgramStateSize];
      if (rev >= 1 && vstProgramStateSize > 0)
         in.ReadGeneric(vstProgramState, vstProgramStateSize);

      if (mPlugin != nullptr)
      {
         ofLog() << "loading vst state for " << mPlugin->getName();

         mPlugin->setStateInformation(vstState, vstStateSize);
         if (rev >= 1 && vstProgramStateSize > 0)
            mPlugin->setCurrentProgramStateInformation(vstProgramState, vstProgramStateSize);
      }
      else
      {
         TheSynth->LogEvent("Couldn't instantiate plugin to load state for " + mModuleSaveData.GetString("pluginId"), kLogEventType_Error);
      }

      if (rev >= 2)
      {
         int numParamsShowing;
         in >> numParamsShowing;
         for (auto& param : mParameterSliders)
            param.mShowing = false;
         for (int i = 0; i < numParamsShowing; ++i)
         {
            int index;
            in >> index;
            if (index < mParameterSliders.size())
            {
               mParameterSliders[index].mShowing = true;
               if (mParameterSliders[index].mSlider == nullptr)
                  mParameterSliders[index].MakeSlider();
            }
         }
      }
   }
}

void VSTPlugin::ParameterSlider::MakeSlider()
{
   const char* sliderID;
   if (mOwner->mParameterVersion == 0)
      sliderID = mDisplayName.c_str(); //old save files used unstable parameters names
   else
      sliderID = mID.c_str(); //now we use parameter IDs for more stability
   mSlider = new FloatSlider(mOwner, sliderID, -1, -1, 210, 15, &mValue, 0, 1);
   mSlider->SetOverrideDisplayName(mDisplayName);
   mRemoveButton = new ClickButton(mOwner, " X ", -1, -1);
}

void VSTPlugin::OnUIControlRequested(const char* name)
{
   if (mParameterVersion == 0) //legacy path
   {
      for (auto& parameter : mParameterSliders)
      {
         if (parameter.mDisplayName == name && parameter.mSlider == nullptr)
            parameter.MakeSlider();
      }
   }
   else
   {
      for (auto& parameter : mParameterSliders)
      {
         if (parameter.mID == name && parameter.mSlider == nullptr)
            parameter.MakeSlider();
      }
   }
}
